package authx

import (
	"context"
	"gitee.com/dn-jinmin/authx/model"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"strings"
)

const systemRoot = "root"

type Authx interface {
	// 记录API接口
	//  path:method
	//  例如: /api/v1/user:GET
	// 如果存在则不记录
	ApiInsert(ctx context.Context, path, method string) error
	// 根据路径和请求方式查询
	ApiFindByPathAndMethod(ctx context.Context, path, method string) (*model.AuthxApi, error)

	// 新增角色
	RoleInsert(ctx context.Context, role string) error
	// 角色查询
	RoleFindByName(ctx context.Context, role string) (*model.AuthxRole, error)

	// role与api关联
	// 如果存在数据则不记录
	RoleApiInsert(ctx context.Context, roleId, apiId int) error
	RoleApiDelete(ctx context.Context, roleId, apiId int) error

	// user与role关联
	// 如果存在则不记录
	UserRoleInsert(ctx context.Context, userId, roleId int) error
	UserRoleDelete(ctx context.Context, userId, roleId int) error

	// 记录apis分组
	// 	 1. apis在存储的时候需要转化为字符串
	//   2. 如果存在group就对数据进行更新
	GroupsInsert(ctx context.Context, group string, name string, apis []string) error

	RoleGroupInsert(ctx context.Context, roleId int, groups []string) error

	// 设置白名单
	SetWhite(map[string]string)

	HaveAccessTo(ctx context.Context, userId int, path, method string) error
}

type authx struct {
	db *gorm.DB

	// 设置白名单
	white map[string]string

	model.Api
	model.Role
	model.RoleGroup
	model.RoleApi
	model.Groups
	model.UserRole
}

func NewAuthx(dns string) (Authx, error) {
	db, err := gorm.Open(mysql.Open(dns), &gorm.Config{})
	if err != nil {
		return nil, err
	}

	a := &authx{
		db:        db,
		Api:       model.NewApi(db),
		Role:      model.NewRole(db),
		RoleGroup: model.NewRoleGroup(db),
		RoleApi:   model.NewRoleApi(db),
		Groups:    model.NewGroups(db),
		UserRole:  model.NewUserRole(db),
	}

	if err := a.initRole(); err != nil {
		return nil, err
	}

	return a, nil
}

// 初始化超级管理员
func (a *authx) initRole() error {

	_, err := a.Role.FindByName(context.Background(), systemRoot)
	if err == nil {
		return nil
	}

	if err != model.ErrNotFound {
		return err
	}

	return a.Role.Insert(context.Background(), systemRoot)
}

func (a *authx) ApiInsert(ctx context.Context, path, method string) error {
	apiStr := path + ":" + method

	// 检查是否已存在
	_, err := a.Api.FindOne(ctx, apiStr)
	if err == nil {
		return nil // 已存在则不重复插入
	}
	if err != model.ErrNotFound {
		return err
	}

	return a.Api.InsertOne(ctx, apiStr)
}

func (a *authx) ApiFindByPathAndMethod(ctx context.Context, path, method string) (*model.AuthxApi, error) {
	return a.Api.FindOne(ctx, a.apiStr(path, method))
}

func (a *authx) apiStr(path, method string) string {
	return path + ":" + method
}

// ... 保留已有接口定义和结构体定义 ...

func (a *authx) RoleInsert(ctx context.Context, role string) error {
	_, err := a.Role.FindByName(ctx, role)
	if err == nil {
		return nil
	}
	if err != model.ErrNotFound {
		return err
	}

	return a.Role.Insert(ctx, role)
}

func (a *authx) RoleFindByName(ctx context.Context, role string) (*model.AuthxRole, error) {
	return a.Role.FindByName(ctx, role)
}

// 在authx结构体中实现接口方法
func (a *authx) RoleApiInsert(ctx context.Context, roleId, apiId int) error {
	_, err := a.RoleApi.FindByRoleIdAndApiId(ctx, roleId, apiId)
	if err == nil {
		return nil
	}
	if err != model.ErrNotFound {
		return err
	}

	return a.RoleApi.Insert(ctx, roleId, apiId)
}

func (a *authx) RoleApiDelete(ctx context.Context, roleId, apiId int) error {
	return a.RoleApi.Delete(ctx, roleId, apiId)
}

func (a *authx) UserRoleInsert(ctx context.Context, userId, roleId int) error {
	userRole, err := a.UserRole.FindByUserId(ctx, userId)
	if err != nil && err != model.ErrNotFound {
		return err
	}

	if err == model.ErrNotFound {
		return a.UserRole.Insert(ctx, userId, roleId)
	}

	if userRole.RoleId == roleId {
		return nil
	}

	return a.UserRole.Update(ctx, userId, roleId)
}

func (a *authx) UserRoleDelete(ctx context.Context, userId, roleId int) error {
	return a.UserRole.Delete(ctx, userId, roleId)
}

func (a *authx) GroupsInsert(ctx context.Context, group, name string, apis []string) error {
	apiStr := strings.Join(apis, ",")
	groupEntity, err := a.Groups.FindByGroupName(ctx, group)
	if err != nil {
		if err != model.ErrNotFound {
			return err
		}
		return a.Groups.Insert(ctx, group, name, apiStr)
	}

	err = a.Groups.Update(ctx, int64(groupEntity.Id), group, name, apiStr)
	if err != nil {
		return err
	}

	roleGroups, err := a.RoleGroup.FindByGroupIds(ctx, groupEntity.Id)
	if err != nil {
		return err
	}

	apiEntitys, err := a.Api.AllToMap(ctx)
	if err != nil {
		return err
	}

	groupEntity.Apis = apiStr
	for _, roleGroup := range roleGroups {
		roleApis, err := a.roleGroupInsert(ctx, roleGroup.RoleId, apiEntitys, groupEntity)
		if err != nil {
			return err
		}

		a.RoleApi.BatchInsertDelGroupId(ctx, groupEntity.Id, roleApis)
	}

	return nil
}

func (a *authx) RoleGroupInsert(ctx context.Context, roleId int, groups []string) error {

	role, err := a.Role.FindOne(ctx, int64(roleId))
	if err != nil {
		return err
	}
	if role.Role == systemRoot {
		return ErrSystem
	}

	groupLists, err := a.Groups.FindByGroups(ctx, groups)
	if err != nil {
		return err
	}

	apis, err := a.Api.AllToMap(ctx)
	if err != nil {
		return err
	}

	roleApis, err := a.roleGroupInsert(ctx, roleId, apis, groupLists...)
	if err != nil {
		return err
	}

	return a.RoleApi.BatchInsert(ctx, roleId, roleApis)
}

func (a *authx) roleGroupInsert(ctx context.Context, roleId int, apis map[string]*model.AuthxApi, groupLists ...*model.AuthxGroups) ([]*model.AuthxRoleApi, error) {

	var (
		roleApis = make([]*model.AuthxRoleApi, 0, len(groupLists))
		groupIds = make([]int, 0, len(groupLists))
	)
	for _, list := range groupLists {
		groupIds = append(groupIds, list.Id)

		apiArr := strings.Split(list.Apis, ",")
		for _, s := range apiArr {
			if v, ok := apis[s]; ok {
				roleApis = append(roleApis, &model.AuthxRoleApi{
					RoleId:  roleId,
					GroupId: list.Id,
					ApiId:   v.Id,
				})
			}
		}
	}

	if err := a.RoleGroup.BatchInsert(ctx, roleId, groupIds); err != nil {
		return nil, err
	}

	return roleApis, nil
}

func (a *authx) SetWhite(v map[string]string) {
	a.white = v
}

func (a *authx) HaveAccessTo(ctx context.Context, userId int, path, method string) error {
	if a.white != nil {
		m, ok := a.white[path]
		if !ok && m == method {
			return nil
		}
	}

	api, err := a.Api.FindOne(ctx, a.apiStr(path, method))
	if err != nil {
		if err == model.ErrNotFound {
			return nil
		}
		return err
	}

	userRole, err := a.UserRole.FindByUserId(ctx, userId)
	if err != nil {
		if err == model.ErrNotFound {
			return ErrNotAllow
		}
		return err
	}

	role, err := a.Role.FindOne(ctx, int64(userRole.RoleId))
	if err != nil {
		if err == model.ErrNotFound {
			return ErrNotAllow
		}
		return err
	}
	if role.Role == systemRoot {
		return nil
	}

	_, err = a.RoleApi.FindByRoleIdAndApiId(ctx, userRole.RoleId, api.Id)
	if err == nil {
		return nil
	}

	return ErrNotAllow
}
